package com.xdoc.client.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * JSON工具类
 * @author coffish
 */
public class JSON {
    /**
     * 字符串转map
     * @param str
     * @return
     */
    private static Map toMap(String str) {
        //去掉{}
        str = str.substring(1, str.length() - 1);
        Map map = new LinkedHashMap();
        char c;
        boolean strBegin = false;
        StringBuffer sb = new StringBuffer();
        //对象计数
        int nobj = 0;
        //数组计数
        int narr = 0;
        String key = "";
        boolean keyBegin = true;
        boolean objBegin = false;
        for (int i = 0; i < str.length(); i++) {
            c = str.charAt(i);
            if (keyBegin || objBegin) {
                sb.append(c);
            }
            if (strBegin) {
                if (c == '\\' && i + 1 < str.length()) {
                    i++;
                    sb.append(str.charAt(i));
                } else if (c == '\"') {
                    strBegin = false;
                }
            } else if (c == '\"') {
                strBegin = true;
            } else if (c == ',') { //属性结束
                if (nobj == 0 && narr == 0) {
                    if (key.length() > 0 || sb.length() > 0) {
                        sb.setLength(sb.length() - 1);
                        map.put(key, parse(sb.toString()));
                    }
                    key = "";
                    sb.setLength(0);
                    keyBegin = true;
                }
            } else if (c == ':') { //值开始
                if (keyBegin) {
                    sb.setLength(sb.length() - 1);
                    key = decodeString(sb.toString());
                    sb.setLength(0);
                    keyBegin = false;
                    objBegin = true;
                }
            } else if (c == '[') { //对象开始
                if (nobj == 0 && narr == 0) {
                    objBegin = true;
                }
                narr++;
            } else if (c == ']') { //对象结束
                narr--;
                if (nobj == 0 && narr == 0) {
                    if (key.length() > 0 || sb.length() > 0) {
                        map.put(key, parse(sb.toString()));
                    }
                    key = "";
                    sb.setLength(0);
                    objBegin = false;
                }
            } else if (c == '{') { //对象开始
                if (nobj == 0 && narr == 0) {
                    objBegin = true;
                }
                nobj++;
            } else if (c == '}') { //对象结束
                nobj--;
                if (nobj == 0 && narr == 0) {
                    if (key.length() > 0 || sb.length() > 0) {
                        map.put(key, parse(sb.toString()));
                    }
                    key = "";
                    sb.setLength(0);
                    objBegin = false;
                }
            }
        }
        if (sb.length() > 0) {
            map.put(key, decodeString(sb.toString()));
        }
        return map;
    }
    private static List toList(String str) {
        List list = new ArrayList();
        str = str.substring(1, str.length() - 1);
        char c;
        int nobj = 0;
        int narr = 0;
        boolean strBegin = false;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            c = str.charAt(i);
            if (strBegin) {
                if (c == '\\' && i + 1 < str.length()) {
                    sb.append(c);
                    i++;
                    c = str.charAt(i);
                } else if (c == '\"') {
                    strBegin = false;
                }
            } else if (c == '\"') {
                strBegin = true;
            } else if (c == '[') { //对象开始
                narr++;
            } else if (c == ']') { //对象结束
                narr--;
            } else if (c == '{') { //对象开始
                nobj++;
            } else if (c == '}') { //对象结束
                nobj--;
            } else if (c == ',') { //元素分割
                if (nobj == 0 && narr == 0) {
                    list.add(parse(sb.toString()));
                    sb.setLength(0);
                    continue;
                }
            }
            sb.append(c);
        }
        if (sb.length() > 0) {
            list.add(parse(sb.toString()));
        }
        return list;
    }
    public static String stringify(Object obj) throws IOException {
        StringBuilder sb = new StringBuilder();
        Set<Object> chainSet = new HashSet<Object>();
        appendString(sb, obj, chainSet);
        return sb.toString();
    }
    public static Object parse(String str) {
        str = str.trim();
        if (str.startsWith("[") && str.endsWith("]")) {
            return toList(str);
        } else if (str.startsWith("{") && str.endsWith("}")){
            return toMap(str);
        } else {
            return decodeString(str);
        }
    }
    private static void appendString(StringBuilder sb, Object obj, Set<Object> set) throws IOException {
        if (obj == null) {
            sb.append("null");
        } else if (obj instanceof Boolean
                || obj instanceof Number) {
            sb.append(obj.toString());
        } else if (obj instanceof CharSequence
                || obj instanceof Date
                || obj instanceof File
                || obj instanceof URL
                || obj instanceof InputStream) {
            String str = null;
            if (obj.getClass().isPrimitive()
                    || obj instanceof Boolean
                    || obj instanceof Number
                    || obj instanceof CharSequence) {
                str = obj.toString();
            } else if (obj instanceof Date) {
                str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) obj);
            } else if (obj instanceof File) {
                str = IOUtils.toDataURI(((File) obj).getAbsolutePath());
            } else if (obj instanceof URL) {
                str = IOUtils.toDataURI((URL) obj);
            } else if (obj instanceof InputStream) {
                str = IOUtils.toDataURI((InputStream) obj);
            } else {
                str = obj.toString();
            }
            appendString(sb, str);
        } else if (obj instanceof Collection) {
            sb.append("[");
            boolean b = false;
            Iterator<?> it = ((Collection<?>) obj).iterator();
            while (it.hasNext()) {
                if (b) sb.append(",");
                appendString(sb, it.next(), set);
                b = true;
            }
            sb.append("]");
        } else if (obj.getClass().isArray()) {
            sb.append("[");
            boolean b = false;
            int n = Array.getLength(obj);
            for (int i = 0; i < n; i++) {
                if (b) sb.append(",");
                appendString(sb, Array.get(obj, i), set);
                b = true;
            }
            sb.append("]");
        } else if (obj instanceof Map) {
            sb.append("{");
            Map<?, ?> map = (Map<?, ?>) obj;
            boolean b = false;
            Object key;
            Iterator<?> it = map.keySet().iterator();
            while (it.hasNext()) {
                if (b) sb.append(",");
                key = it.next();
                appendString(sb, key.toString());
                sb.append(":");
                appendString(sb, map.get(key), set);
                b = true;
            }
            sb.append("}");
        } else {
            sb.append("{");
            if (!set.contains(obj) && obj.getClass() != Object.class && obj.getClass() != Class.class) {
                set.add(obj);
                try {
                    List<Method> getters = findGetters(obj);
                    boolean b = false;
                    for (Method method : getters) {
                        if (b) sb.append(",");
                        appendString(sb, findGetterName(method));
                        sb.append(":");
                        appendString(sb, method.invoke(obj, new Object[0]), set);
                        b = true;
                    }
                } catch (Exception e) {
                    throw new IOException(e);
                }
                set.remove(obj);
            }
            sb.append("}");
        }
    }
    private static List<Method> findGetters(Object obj) {
        List<Method> getters = new ArrayList<Method>();
        String name;
        for (Method method : obj.getClass().getMethods()) {
            name = method.getName();
            if (Modifier.isStatic(method.getModifiers())
                    || method.getReturnType().equals(Void.TYPE)
                    || method.getParameterTypes().length != 0
                    || method.getReturnType() == ClassLoader.class
            ) {
                continue;
            }
            if (name.startsWith("get") && name.length() >= 4 && !name.equals("getClass")
                    || name.startsWith("is") && name.length() >= 3) {
                getters.add(method);
            }
        }
        return getters;
    }
    private static String findGetterName(Method method) {
        String name = method.getName();
        if (name.startsWith("get")) {
            name = name.substring(3);
        } else if (name.startsWith("is")) {
            name = name.substring(2);
        }
        if (name.length() > 1
                && Character.isUpperCase(name.charAt(1))
                && Character.isUpperCase(name.charAt(0))){
            return name;
        }
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }
    private static void appendString(StringBuilder sb, String str) {
        sb.append("\"");
        char c;
        for (int i = 0; i < str.length(); i++) {
            c = str.charAt(i);
            if (c == '\\') {
                sb.append("\\\\");
            } else if (c == '/') {
                sb.append("\\/");
            } else if (c == '\n') {
                sb.append("\\n");
            } else if (c == '\r') {
                sb.append("\\r");
            } else if (c == '\t') {
                sb.append("\\t");
            } else if (c == '\'') {
                sb.append("\\\'");
            } else if (c == '\"') {
                sb.append("\\\"");
            } else {
                sb.append(c);
            }
        }
        sb.append("\"");
    }
    /**
     * 解码
     * @param str
     * @return
     */
    private static String decodeString(String str) {
        str = str.trim();
        if (str.length() >=2 && str.startsWith("\"") && str.endsWith("\"")) {
            str = str.substring(1, str.length() - 1);
            StringBuffer sb = new StringBuffer();
            char c;
            for (int i = 0; i < str.length(); i++) {
                c = str.charAt(i);
                if (c == '\\' && i + 1 < str.length()) {
                    i++;
                    c = str.charAt(i);
                    if (c == 'n') {
                        c = '\n';
                    } else if (c == 'r') {
                        c = '\r';
                    } else if (c == 't') {
                        c = '\t';
                    } else if (c == 'u') {
                        if (i + 5 <= str.length()) {
                            try {
                                c = (char) Integer.parseInt(str.substring(i + 1, i + 5), 16);
                                i += 4;
                            } catch (Exception e) {
                            }
                        }
                    }
                }
                sb.append(c);
            }
            str = sb.toString();
        } else if (str.equals("null")) {
            str = "";
        }
        return str;
    }
}
